package org.atlantis.grosbeak.pot;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.atlantis.grosbeak.lang.Exceptions;
import org.atlantis.grosbeak.lang.Mirror;
import org.atlantis.grosbeak.logger.Logger;
import org.atlantis.grosbeak.logger.LoggerFactory;
import org.atlantis.grosbeak.pot.elm.Arg;
import org.atlantis.grosbeak.pot.elm.FirstIndex;
import org.atlantis.grosbeak.pot.elm.MaxIndex;
import org.atlantis.grosbeak.pot.transaction.TransactionProvider;

/**
 * SQL执行的基类。所有的SQL执行器必须继承此类。
 * 
 * @author @author <a href="mailto:seenoevil.cn@gmail.com">SeeNoEvil</a>
 * 
 */
abstract class SQLHandler {

	protected final Logger logger = LoggerFactory.getLogger(SQLHandler.class);
	protected static final Object[] EMPTY_ARGS = new Object[0];

	/**
	 * 执行一条SQL语句。
	 * 
	 * @param txProvider
	 *            事务产生器。
	 * @param args
	 *            SQL语句中所需要的参数。
	 * @return SQL语句的执行结果。
	 */
	public abstract Object handle(TransactionProvider txProvider,
			Object... args);

	/**
	 * 获取指定方法的参数注解。
	 * 
	 * @param method
	 *            需要获取参数注解的方法。
	 * @return
	 * @throws PotException
	 */
	Annotation[] getArgsAnnotations(Method method) throws PotException {
		Class<?>[] types = method.getParameterTypes();
		Annotation[][] ass = method.getParameterAnnotations();
		Annotation[] paramAnns = new Annotation[types.length];
		for (int i = 0; i < types.length; i++) {
			paramAnns[i] = getArgAnnotation(method, types[i], ass[i]);
		}
		return paramAnns;
	}

	/**
	 * 获取指定参数的注解。如果有多个注解，那么就过滤出最重要的一个注解。<br>
	 * 注：@MaxIndex、@FirstIndex两个注解已经具有@Arg注解的功能，所以如果有了@MaxIndex或@FirstIndex注解就不能有@Arg注解。
	 * 
	 * @param method
	 *            参数所在的方法。
	 * @param type
	 *            参数的类型。
	 * @param anns
	 *            一个参数有多个注解，但是在Grosbeak里面没有参数只有一个注解。
	 * @return
	 * @throws PotException
	 */
	Annotation getArgAnnotation(Method method, Class<?> type, Annotation anns[])
			throws PotException {
		boolean firstIndex = false;
		boolean maxIndex = false;
		boolean arg = false;
		Annotation paramAnn = null;
		for (Annotation ann : anns) {
			if (ann.annotationType().equals(FirstIndex.class)) {
				firstIndex = true;
				paramAnn = ann;
			}
			if (ann.annotationType().equals(MaxIndex.class)) {
				maxIndex = true;
				paramAnn = ann;
			}
			if (ann.annotationType().equals(Arg.class)) {
				arg = true;
				paramAnn = ann;
			}
		}
		if (paramAnn == null)
			throw Exceptions
					.makeThrow(
							PotException.class,
							"Missing @Arg or @FirstIndex or @MaxIndex on the parameters of the method: '%s'.",
							method.getName());
		if (firstIndex && maxIndex)
			throw Exceptions
					.makeThrow(
							PotException.class,
							"The @FirstIndex and @MaxIndex can not exists in method '%s' at same time.",
							method.getName());
		if (firstIndex && !isInt(type))
			throw Exceptions.makeThrow(PotException.class,
					"The @FirstIndex parameter must be int.");
		if (maxIndex && !isInt(type))
			throw Exceptions.makeThrow(PotException.class,
					"The @MaxIndex parameter must be int.");
		if (arg && firstIndex)
			throw Exceptions
					.makeThrow(
							PotException.class,
							"@Arg and @FirstIndex can not on the same parameter in method: '%s'.",
							method.getName());
		if (arg && maxIndex)
			throw Exceptions
					.makeThrow(
							PotException.class,
							"@Arg and @MaxIndex can not on the same parameter in method: '%s'.",
							method.getName());
		return paramAnn;
	}

	/**
	 * 判断一个类型是不是int类型。
	 * 
	 * @param type
	 *            需要判断的类型。
	 * @return
	 */
	boolean isInt(Class<?> type) {
		return int.class.equals(type) || Integer.class.equals(type);
	}

	/**
	 * SQL语句中有各种参数，而方法中也会传入相应的参数，但是SQL语句中参数的位置和方法中传入参数的位置可能不同。
	 * 该方法就是获取一个指定名称的方法参数的位置（从0开始）。如：
	 * <pre>
	 * public void testMethod(@Arg("f1") int fi, @Arg("mi") int mi, @Arg("name") String name) { ... }
	 * 那么对这个方法使用 getInexOfArgByName(testMethod, {testMethod方法中的三个参数注解}, "name")方法得到的结果就是2。
	 * </pre>
	 * @param method
	 * @param anns
	 * @param argName
	 * @return
	 */
	int getIndexOfArgByName(Method method, Annotation[] anns, String argName) {
		int pos = argName.indexOf('.');
		if (pos != -1)
			argName = argName.substring(0, pos);
		for (int i = 0; i < anns.length; i++) {
			Annotation ann = anns[i];
			if (ann.annotationType().equals(Arg.class)) {
				if (((Arg) ann).value().equals(argName)) {
					return i;
				}
			}
		}
		throw Exceptions.makeThrow(PotException.class,
				"Missing @Arg(\"%s\") in parameter of method: '%s'", argName,
				method.getName());
	}
	
	/**
	 * 获取SQL中的参数的值。
	 * @param argIndex
	 * @param methods
	 * @param args
	 * @return
	 * @throws PotException
	 */
	Object[] getSqlParams(int[] argIndex, Method[] methods, Object[] args)
			throws PotException {
		if (argIndex.length == 0)
			return EMPTY_ARGS;
		Object[] reorderedArgs = new Object[argIndex.length];
		for (int i = 0; i < argIndex.length; i++) {
			Method method = methods[i];
			if (method == null) {
				reorderedArgs[i] = args[argIndex[i]];
			} else {
				try {
					reorderedArgs[i] = method.invoke(args[argIndex[i]]);
				} catch (Exception e) {
					throw Exceptions.makeThrow(PotException.class, e,
							"Fail to invoke method: '%s'.", method.getName());
				}
			}
		}
		return reorderedArgs;
	}

	/**
	 * 以一个字符串的形式来表示方法。如：
	 * 
	 * <pre>
	 * 方法：public void testMethod(@FirstIndex @MaxIndex int fi, @MaxIndex int mi) { ... }
	 * 那么获得的字符串表示就是：void testMethod(int,int)
	 * </pre>
	 * 
	 * @param method
	 * @return
	 */
	protected String methodToString(Method method) {
		StringBuilder sb = new StringBuilder(128);
		sb.append(method.getReturnType().getSimpleName()).append(' ').append(
				method.getName()).append('(');
		Class<?>[] types = method.getParameterTypes();
		for (Class<?> type : types) {
			sb.append(type.getSimpleName()).append(',');
		}
		if (types.length > 0)
			sb.deleteCharAt(sb.length() - 1);
		sb.append(')');
		return sb.toString();
	}

	/**
	 * 根据SQL语句中的参数获取字段名。如：
	 * 
	 * <pre>
	 * SQL语句：SELECT * FROM USERS u WHERE u.ID = :user.id and u.NAME = user.name;
	 * 其中的参数就是：user.id和user.name，那么根据这两个参数就可以得到id和name两个实体对象
	 * 中的属性名。
	 * </pre>
	 * 
	 * @param argName
	 * @return
	 */
	String getPropertyName(String argName) {
		int pos = argName.indexOf('.');
		if (pos == -1)
			return null;
		return argName.substring(pos + 1);
	}

	/**
	 * 获取一个字段的Getter方法。
	 * 
	 * @param clazz
	 *            字段所在的类。
	 * @param fieldName
	 *            字段名称。
	 * @return
	 */
	Method getGetterMethod(Class<?> clazz, String fieldName) {
		Mirror<?> me = Mirror.me(clazz);
		return me.getGetter(fieldName);
	}
}
